package com.kinyx.framework.db.configs;

import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import javax.persistence.Entity;

import com.kinyx.framework.Configs;
import com.kinyx.framework.bean.PackageScaner;
import com.kinyx.framework.db.daos.DaoConfigs;
import com.kinyx.framework.db.datasource.Datasource;
import com.kinyx.framework.db.defines.INamingStrategy;
import com.kinyx.framework.db.defines.IPKGenerator;
import com.kinyx.framework.db.dialect.Dialect;
import com.kinyx.framework.db.vo.ColumnInfo;
import com.kinyx.framework.db.vo.MappingInfo;
import com.kinyx.framework.db.vo.TableInfo;
import com.kinyx.framework.enums.impl.ErrorCode1;
import com.kinyx.framework.exception.KRuntimeException;
import com.kinyx.framework.file.KPropertiesReader;
import com.kinyx.framework.log.Log;
import com.kinyx.framework.log.Logger;
import com.kinyx.framework.reflect.KBeanUtils;

import lombok.Getter;

/**
 * 数据库配置
 */
public abstract class DBConfigs {

	/** 日志对象 */
	@Getter
	private final Log logger = Logger.getLog(this.getClass());

	/** 配置属性 */
	private Properties properties;

	/** 数据源 */
	@Getter
	private Datasource datasource;
	/** 数据库方言 */
	@Getter
	private Dialect dialect;
	/** 数据库访问自定义参数 */
	@Getter
	private DaoConfigs daoConfigs;

	/** 实体类扫描包 */
	private PackageScaner packagesEntity;

	/** 命名策略实现类 */
	@Getter
	private INamingStrategy NamingStrategy;
	/** 主键生成器实现类 */
	@Getter
	private IPKGenerator PKGenerator;

	/** @return 配置文件名 */
	protected String getConfigsFileName() { return "application-db.properties"; }

	/** @return 配置键唯一标识 */
	public abstract String getKey();

	private String getConfigKeyPrefix(final String key) { return "db." + this.getKey() + "." + key; }

	// 加载字符串配置
	private String loadStringConfig(final String key) {
		if (this.logger.isTraceEnabled()) { this.logger.trace("load config [" + key + "] from config file [" + this.getConfigsFileName() + "]"); }
		final String value = KPropertiesReader.getStringValue(this.properties, key, null);
		if ((value == null) && !Configs.FRAMEWORK_DEBUG) { throw new KRuntimeException(ErrorCode1.CODE_ERROR_501).param("fileName", this.getConfigsFileName()).param("key", key); }
		if (this.logger.isTraceEnabled()) { this.logger.trace("loaded config [" + key + " = " + value + "] from config file [" + this.getConfigsFileName() + "]"); }
		return value;
	}

	// 数据源
	private void initDatasource() {
		if (this.properties == null) { return; }
		final String configKeyPrefix = this.getConfigKeyPrefix("datasource.");
		final String driverClassName = this.loadStringConfig(configKeyPrefix + "driver-class-name");
		final String jdbcUrl = this.loadStringConfig(configKeyPrefix + "jdbc-url");
		final String username = this.loadStringConfig(configKeyPrefix + "username");
		final String password = this.loadStringConfig(configKeyPrefix + "password");
		this.datasource = new Datasource(driverClassName, jdbcUrl, username, password);
	}

	// 数据库方言
	private void initDialect() {
		if (this.properties == null) { return; }
		this.dialect = DBConfigsHelper.getDialectByDriverClassName(this.datasource.getDriverClassName());
	}

	// 数据库访问自定义参数
	private void initDaoConfigs() {
		if (this.properties == null) { return; }
		final String configKeyPrefix = this.getConfigKeyPrefix("dao.");
		final int slowChangeTime = KPropertiesReader.getIntegerValue(this.properties, configKeyPrefix + "slowChangeTime", 1000);
		final boolean showChangeLog = KPropertiesReader.getBooleanValue(this.properties, configKeyPrefix + "showChangeLog", true);
		final int slowBatchChangeTime = KPropertiesReader.getIntegerValue(this.properties, configKeyPrefix + "slowBatchChangeTime", 10000);
		final boolean showBatchChangeLog = KPropertiesReader.getBooleanValue(this.properties, configKeyPrefix + "showBatchChangeLog", true);
		final int slowQueryTime = KPropertiesReader.getIntegerValue(this.properties, configKeyPrefix + "slowQueryTime", 1000);
		final boolean showQueryLog = KPropertiesReader.getBooleanValue(this.properties, configKeyPrefix + "showQueryLog", true);
		this.daoConfigs = new DaoConfigs(slowChangeTime, showChangeLog, slowBatchChangeTime, showBatchChangeLog, slowQueryTime, showQueryLog);
	}

	// 实体类扫描包
	private void packagesEntity(final String configValue) {
		final String defaultValue = "com.kinyx.**.entities.impl";
		if (configValue == null) {
			if (this.properties == null) {
				this.packagesEntity = PackageScaner.of(defaultValue);
			} else {
				this.packagesEntity = PackageScaner.of(KPropertiesReader.getStringValue(this.properties, this.getConfigKeyPrefix("packagesEntity"), defaultValue));
			}
		} else {
			this.packagesEntity = PackageScaner.of(configValue);
		}
	}

	// 命名策略实现类
	private void initNamingStrategy(final String configValue) {
		final INamingStrategy defaultValue = new NamingStrategyDefault();
		if (configValue == null) {
			if (this.properties == null) {
				this.NamingStrategy = defaultValue;
			} else {
				this.NamingStrategy = KPropertiesReader.getInstanceValue(this.properties, this.getConfigKeyPrefix("NamingStrategy"), defaultValue);
			}
		} else {
			this.NamingStrategy = KBeanUtils.newInstance(configValue);
		}
	}

	// 主键生成器实现类
	private void initPKGenerator(final String configValue) {
		final PKGenerator defaultValue = new PKGenerator();
		if (configValue == null) {
			if (this.properties == null) {
				this.PKGenerator = defaultValue;
			} else {
				this.PKGenerator = KPropertiesReader.getInstanceValue(this.properties, this.getConfigKeyPrefix("PKGenerator"), defaultValue);
			}
		} else {
			this.PKGenerator = KBeanUtils.newInstance(configValue);
		}
	}

	void setProperties(final String configFileName, final String packagesEntity, final String NamingStrategy, final String PKGenerator) {
		// 配置属性
		this.properties = configFileName == null ? null : KPropertiesReader.read(configFileName);

		// 数据源
		this.initDatasource();
		// 数据库方言
		this.initDialect();
		// 数据库访问自定义参数
		this.initDaoConfigs();

		// 实体类扫描包
		this.packagesEntity(packagesEntity);

		// 命名策略实现类
		this.initNamingStrategy(NamingStrategy);
		// 主键生成器实现类
		this.initPKGenerator(PKGenerator);
	}

	@SuppressWarnings("unchecked")
	void afterPropertiesSet() {
		// 扫描实体类
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("scan entities from package【" + this.packagesEntity.toString() + "】");
		}
		this.packagesEntity.scanClasses(true, true, Entity.class).forEach(clazz -> {
			if (this.logger.isTraceEnabled()) { this.logger.trace("entity class【" + clazz.getName() + "】 scanned from package【" + this.packagesEntity.toString() + "】"); }
			// 缓存管理实体类
			final TableInfo tableInfo = new TableInfo(clazz, this.NamingStrategy);
			this.entityTableInfos.put(tableInfo.getMappingClass().getName(), tableInfo);
		});
	}

	/** 缓存的Entity对应的信息 */
	private HashMap<String, TableInfo> entityTableInfos = new HashMap<>();
	/** 缓存的Bean对应的信息 */
	private HashMap<String, MappingInfo> beanTableInfos = new HashMap<>();

	// for JDBCDao queryOneById、save、remove
	// 只需支持Entity，因此直接从缓存取即可。
	public TableInfo getTableInfo(final Class<?> clazz) { return this.entityTableInfos.get(clazz.getName()); }

	// for JDBCDao ResultSet to List<Bean> or Bean
	// 需要支持非Entity的Bean
	public List<ColumnInfo> getColumnInfos(final Class<?> clazz) {
		final TableInfo tableInfo = this.getTableInfo(clazz);
		if (tableInfo == null) {// Bean
			MappingInfo mappingInfo = this.beanTableInfos.get(clazz.getName());
			if (mappingInfo != null) { return mappingInfo.getColumnInfos(); } // 已经缓存，直接取。
			// 未缓存，创建、缓存、返回。
			mappingInfo = new MappingInfo(clazz, this.NamingStrategy);
			this.beanTableInfos.put(mappingInfo.getMappingClass().getName(), mappingInfo);
			return mappingInfo.getColumnInfos();
		} else {// Entity
			return tableInfo.getColumnInfos();
		}
	}

}
